package info.motteke.mixivoice4j.http;

import info.motteke.mixivoice4j.Body;
import info.motteke.mixivoice4j.Comment;
import info.motteke.mixivoice4j.CommentBuilder;
import info.motteke.mixivoice4j.DataBuilderFactory;
import info.motteke.mixivoice4j.Entry;
import info.motteke.mixivoice4j.EntryBuilder;
import info.motteke.mixivoice4j.EntryUpdateListener;
import info.motteke.mixivoice4j.ImageUrl;
import info.motteke.mixivoice4j.MixiVoice;
import info.motteke.mixivoice4j.MixiVoiceCache;
import info.motteke.mixivoice4j.MixiVoiceObserver;
import info.motteke.mixivoice4j.VoiceCreateListener;
import info.motteke.mixivoice4j.PostDate;
import info.motteke.mixivoice4j.UpdateInfo;
import info.motteke.mixivoice4j.UpdateStrategy;
import info.motteke.mixivoice4j.User;
import info.motteke.mixivoice4j.UserBuilder;
import info.motteke.mixivoice4j.UserUpdateListener;
import info.motteke.mixivoice4j.UpdateStrategy.Plan;
import info.motteke.mixivoice4j.http.html.Home;
import info.motteke.mixivoice4j.http.html.RecentVoice;
import info.motteke.mixivoice4j.http.html.RecentVoiceComment;
import info.motteke.mixivoice4j.http.html.RecentVoiceCommentUser;
import info.motteke.mixivoice4j.http.html.RecentVoiceEntry;
import info.motteke.mixivoice4j.http.html.RecentVoiceEntryUser;
import info.motteke.mixivoice4j.http.html.RecentVoiceFavoriteUser;
import info.motteke.mixivoice4j.http.json.comment.create.CreateCommentResponse;
import info.motteke.mixivoice4j.http.json.comment.create.CreateCommentResult;
import info.motteke.mixivoice4j.http.json.comment.create.CreateCommentResultInfo;
import info.motteke.mixivoice4j.http.json.comment.get.GetCommentResponse;
import info.motteke.mixivoice4j.http.json.comment.get.JsonComment;
import info.motteke.mixivoice4j.http.json.favorite.get.Favorite;
import info.motteke.mixivoice4j.http.json.favorite.get.GetFavoriteResponse;
import info.motteke.mixivoice4j.http.json.photo.get.GetPhotoThumbnailUrlResponse;

import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * 実際にMixiからボイスを取得します。
 *
 * @author Y.Wakamatsu
 *
 */
public class BasicMixiVoice implements MixiVoice {

    /** ロガーです。 */
    private static final Log log = LogFactory.getLog(BasicMixiVoice.class);

    /** Mixiにアクセスするクライアントです。 */
    private final MixiHttpClient client;

    /** Mixiボイスの各種データを保存するキャッシュです。 */
    private DataBuilderFactory factory;

    private MixiVoiceObserver observer;

    private UpdateInfo previousUpdate;

    /** mixiでユーザを認識するためのキーです。 */
    private String postKey = null;

    /** mixiにログインしているユーザです。 */
    private User viewer;

    @SuppressWarnings("unused")
    private MixiVoiceCache cache;

    public BasicMixiVoice() {
        client = new MixiHttpClient();
    }

    /**
     * ログインを行い、その成否を返す。
     *
     * @param userId
     *            ユーザID。現在はメールアドレスになっている。
     * @param password
     *            パスワード。
     * @param nextUrl
     *            ログインした後表示するページ。
     */
    @Override
    public synchronized void login(String userId, String password) {
        this.client.login(userId, password);
        initUserData();
        return;
    }

    @Override
    public synchronized void logout() {
        this.client.logout();
    }

    @Override
    public synchronized void close() {
        this.client.close();
    }

    @Override
    public synchronized List<Entry> getVoices(Date from) {
        int i = 1;
        List<Entry> voices = new ArrayList<Entry>();

        while (true) {
            boolean done = false;
            List<Entry> list = getRecentVoice(i);

            if (list == null) {
                break;
            }

            for (Iterator<Entry> itr = list.iterator(); itr.hasNext();) {
                Entry entry = itr.next();

                if (entry.getPostDate().before(from)) {
                    itr.remove();
                    done = true;
                }
            }

            voices.addAll(list);

            if (done) {
                break;
            }
        }

        return voices;
    }

    @Override
    public synchronized List<Entry> getVoices(int page) {
        if (page < 1) {
            throw new IllegalArgumentException("引数は0以下に出来ません。");
        }
        List<Entry> voices = new ArrayList<Entry>();

        for (int i = 1; i <= page; i++) {
            List<Entry> list = getRecentVoice(i);
            if (list == null) {
                break;
            }
            voices.addAll(list);
        }
        return voices;
    }

    @Override
    public synchronized Entry update(Entry voice) {
        if (voice == null) {
            throw new NullPointerException("引数はnullに出来ません。");
        }
        log.debug(voice + "を更新します。");
        updateFavorite(voice);
        updateChildren(voice);

        log.debug(voice);
        return voice;
    }

    @Override
    public synchronized void post(String body) {
        observer.start();
        RecentVoice rv = client.createEntry(viewer, body, postKey);
        parseRecentVoice(rv);
        observer.end();
    }

    @Override
    public synchronized void post(String body, InputStream image, String imageName) {
        observer.start();
        RecentVoice rv = client.createEntry(viewer, body, postKey, image, imageName);
        parseRecentVoice(rv);
        observer.end();
    }

    @Override
    public synchronized void comment(Entry voice, String body) {
        observer.start();
        CreateCommentResponse res = client.createComment(voice, postKey, viewer, body);

        CreateCommentResult result = res.getResult();
        CreateCommentResultInfo info = result.getInfo();

        long id = PostDate.date2id(info.getPostedTime());

        factory.commentTo(voice)
               .identifiedBy(id, viewer)
               .body(info.getBody())
               .build();

        observer.end();
    }

    @Override
    public synchronized InputStream getImage(String url) {
        return this.client.getImage(url);
    }

    @Override
    public synchronized void removeVoice(Entry voice) {
        client.deleteEntry(voice);
    }

    @Override
    public synchronized void removeVoice(Comment voice) {
        client.deleteComment(voice, viewer);
    }

    @Override
    public synchronized void removeFavorite(Entry voice) {
        removeFavorite(voice, viewer);
    }

    @Override
    public synchronized void removeFavorite(Entry voice, User user) {
        client.deleteFavorite(voice, user, viewer, postKey);
    }

    @Override
    public synchronized void addEntryUpdateListener(EntryUpdateListener listener) {
        observer.addEntryUpdateListener(listener);
    }

    @Override
    public synchronized void addUserUpdateListener(UserUpdateListener listener) {
        observer.addUserUpdateListener(listener);
    }

    @Override
    public synchronized void addVoiceCreateListener(VoiceCreateListener listener) {
        observer.addVoiceCreateListener(listener);
    }

    @Override
    public synchronized void update(UpdateStrategy strategy) {
        log.info("update start");
        observer.start();
        Plan plan = strategy.getUpdatePlan(previousUpdate);
        List<Entry> entries = newList();

        switch (plan.getUnit()) {
            case DATE:
                Date date = plan.getDate();
                log.info(new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(date) + "までのつぶやきを取得します。");
                entries = getVoices(date);
                break;
            case PAGE:
                int page = plan.getPage();
                log.info(page + "ページまでのつぶやきを取得します。");
                entries = getVoices(page);
                break;
        }

        for (Entry entry : entries) {
            update(entry);
        }

        observer.end();
        log.info("update end");
    }

    /**
     * 最新のつぶやきを取得します。
     *
     * @return
     */
    private List<Entry> getRecentVoice(int page) {
        RecentVoice rv = this.client.getRecentVoice(page, this.viewer);
        return parseRecentVoice(rv);
    }

    private List<Entry> parseRecentVoice(RecentVoice rv) {
        List<Entry> entries = new ArrayList<Entry>();
        UserBuilder ub = factory.getUserBuilder();

        for(RecentVoiceEntry rve : rv.getVoiceList() ){
            EntryBuilder eb = factory.newEntry();
            RecentVoiceEntryUser rveu = rve.getUser();
            User user = ub.getUser(rveu.getId(), rveu.getNickName(), rveu.getThumbnail());

            eb.identifiedBy(rve.getId(), user)
              .body(rve.getBody());

            ImageUrl thumbnail = rve.getImageThumbnailUrl();
            if (thumbnail != null) {
                String imageId = rve.getImageId();
                String imagePostKey = rve.getImagePostKey();
                GetPhotoThumbnailUrlResponse res;
                res = client.getPhotoThumbnailUrl(viewer, user, imageId, imagePostKey);

                ImageUrl image = new StringImageUrl(res.getResult());

                eb.image(thumbnail, image);
            }

            for (RecentVoiceComment rvc : rve.getComments()) {
                RecentVoiceCommentUser rvcu = rvc.getUser();
                User commentOwner = ub.getUser(rvcu.getId(), rvcu.getNickName(), rvcu.getThumbnail());
                eb.addComment()
                  .identifiedBy(rvc.getId(), commentOwner)
                  .body(rvc.getBody());
            }

            for (RecentVoiceFavoriteUser rvfu : rve.getFavorites()) {
                User favoriteUser = ub.getUser(rvfu.getId(), rvfu.getNickName(), rvfu.getThumbnail());
                eb.favorite(favoriteUser);
            }

            Entry entry = eb.build();
            entry.setKnownCommentNumber(rve.getCommentCount());
            entry.setKnownFavoriteNumber(rve.getFavoritesCount());
            log.debug(entry);
            entries.add(entry);
        }

        return entries;
    }

    private void updateChildren(Entry voice) {
        if (!(voice.getAllComments().size() < voice.getKnownCommentNumber())) {
            return;
        }
        if (log.isDebugEnabled()) {
            log.debug(voice.getUser().getNickName() + " : " + voice.getId());
            log.debug("検出された" + voice.getKnownCommentNumber() + "件のコメントに対して" +
                      voice.getAllComments().size() + "件のコメントしか保持していないため、更新を行います。");
        }

        GetCommentResponse res = this.client.getComments(voice, this.postKey, 0);

        if (res.errorOccurs()) {
            System.out.println(res.getError().getMessage());
            return;
        }

        List<Comment> children = extractChildVoice(res, voice);

        voice.addAllComments(children);
        voice.setKnownCommentNumber(children.size());
    }

    private List<Comment> extractChildVoice(GetCommentResponse res, Entry entry) {
        List<Comment> voices = new ArrayList<Comment>();


        UserBuilder builder = factory.getUserBuilder();

        for (JsonComment comment : res.getResult().getCommentLoop()) {
            long userId = comment.getCommentMemberId();
            String nickname = comment.getNickname();
            ImageUrl thumbnail = comment.getThumbnail();


            User user = builder.getUser(userId, nickname, thumbnail);

            long posttime = comment.getCommentPostTime();
            Body body = comment.getBody();

            if (body == null) {
                throw new IllegalArgumentException("bodyがnullです。");
            }

            CommentBuilder cb = factory.commentTo(entry)
                                       .identifiedBy(posttime, user)
                                       .body(body);

            voices.add(cb.build());
        }

        return voices;
    }

    private void updateFavorite(Entry voice) {
        if (!(voice.getAllFavorites().size() < voice.getKnownFavoriteNumber())) {
            return;
        }
        if (log.isDebugEnabled()) {
            log.debug(voice.getUser().getNickName() + " : " + voice.getId());
            log.debug("検出された" + voice.getKnownFavoriteNumber() + "件のイイネ！に対して" +
                      voice.getAllFavorites().size() + "件のイイネ！しか保持していないため、更新を行います。");
        }

        GetFavoriteResponse res = this.client.getFavorites(voice, this.postKey);

        if (res.getError() != null) {
            return;
        }

        Set<User> favs = new LinkedHashSet<User>();

        UserBuilder ub = factory.getUserBuilder();
        for (Favorite f : res.getResult().getFeedbackLoop()) {
            User u = ub.getUser(f.getUserId(), f.getNickname(), null);
            favs.add(u);
        }

        voice.addAllFavorites(favs);
        voice.setKnownFavoriteNumber(favs.size());
    }

    private void initUserData() {
        Home home = this.client.getHome();

        this.viewer = factory.getUserBuilder()
                             .getUser(home.getUserId(), home.getNickName(), home.getThumbnail());
        this.postKey = home.getPostKey();
    }

    private <E> List<E> newList() {
        return new ArrayList<E>();
    }

    @Override
    public void setDataBuilderFactory(DataBuilderFactory factory) {
        this.factory = factory;
    }

    @Override
    public void setMixiVoiceCache(MixiVoiceCache cache) {
        this.cache = cache;
    }

    @Override
    public void setMixiVoiceObserver(MixiVoiceObserver observer) {
        this.observer = observer;
    }
}
